Micron Document
ZgRNS Git Node

Node / mirrors / micropython-reticulum / files / firmware / example_proxy.py

Displaying Raw • Download

firmware/example_proxy.py rns-152-frame-hardening (97447676) Text, 10.98 KB

T8b949e"""
µReticulum Proxy: USB Serial <-> LoRa Chat Bridge
==================================================
Turns the RP2040 into a transparent serial<->Reticulum proxy.

On your laptop, open a terminal program (screen / minicom / PuTTY / Tera
Term) on the RP2040's USB CDC port. Anything you type is sent as an LXMF
message over the configured interface (E32 LoRa in your case). Anything
received from a peer is printed back to the same terminal.

Commands (lines starting with /):
/help show this help
/peers list known peers
/to <hex> set current chat target by hex-hash prefix
/me show this node's LXMF address
/name show this node's display name
/announce broadcast my identity now
/quit shutdown and return to the MicroPython REPL

Anything that does NOT start with / is sent as a chat message to the
current target peer.

Auto-targeting:
* If DEFAULT_PEER below is set to a peer's hex hash, that peer is
auto-selected once it announces or we receive a message from it.
* Otherwise, the first peer we see (announce OR incoming message)
becomes the current target.
* Any incoming message updates the current target to its sender, so
replying is always automatic.

Setup:
1. Edit config.py: set NODE_NAME, configure E32 interface.
IMPORTANT: set DEBUG = 0 in config.py so the urns library does not
print log noise onto your chat terminal.
2. Copy urns/, config.py, example_proxy.py to the device.
3. From the REPL: import example_proxy
4. Connect from your laptop with a serial terminal. RP2040 USB CDC
ignores the baud rate, so any value works (115200 is conventional).
"""

Tff7b72from T7ee787config Tff7b72import Te6edf3NODE_NAMETb4b4b4, Te6edf3CONFIG

Tff7b72import T7ee787gc
Te6edf3gcTff7b72.Td2a8ffcollectTb4b4b4(Tb4b4b4)


T8b949e# ---- Proxy options ----------------------------------------------------

T8b949e# Hex hash of a specific peer to auto-target. Leave as None to auto-target
T8b949e# whichever peer is seen first.
Te6edf3DEFAULT_PEER Tff7b72= Tff7b72None

T8b949e# Set to True if you want the local terminal to echo your sent messages
T8b949e# back so you can see your own outbound traffic in the log.
Te6edf3SHOW_OUTGOING Tff7b72= Tff7b72True

T8b949e# When True, the proxy announces itself once a second after boot and then
T8b949e# every 2 minutes. Each announce blocks the event loop for ~5-7s of pure
T8b949e# Python crypto on rp2040, during which input is unresponsive. Set to
T8b949e# False to start silent — you can trigger announces manually with the
T8b949e# /announce command at any time.
Te6edf3AUTO_ANNOUNCE Tff7b72= Tff7b72False

T8b949e# When True, prints a heartbeat from the chat loop every few seconds so
T8b949e# you can confirm the loop is being scheduled by the asyncio event loop.
Te6edf3HEARTBEAT Tff7b72= Tff7b72False


T8b949e# ---- Internal state ---------------------------------------------------

Te6edf3_current_peer Tff7b72= Tff7b72None T8b949e# bytes: destination hash of current chat target
Te6edf3_peer_names Tff7b72= Tb4b4b4{Tb4b4b4} T8b949e# dest_hash bytes -> display name string


Tff7b72def Td2a8ff_shortTb4b4b4(Te6edf3hTb4b4b4)Tb4b4b4:
Tff7b72return Te6edf3hTff7b72.Td2a8ffhexTb4b4b4(Tb4b4b4)Tb4b4b4[Tb4b4b4:T79c0ff8Tb4b4b4] Tff7b72if Te6edf3h Tff7b72else Ta5d6ff"Ta5d6ff????????Ta5d6ff"


Tff7b72def Td2a8ff_set_peerTb4b4b4(Te6edf3dest_hashTb4b4b4, Te6edf3announce_changeTff7b72=Tff7b72TrueTb4b4b4)Tb4b4b4:
Tff7b72global Te6edf3_current_peer
Tff7b72if Te6edf3_current_peer Tff7b72== Te6edf3dest_hashTb4b4b4:
Tff7b72return
Te6edf3_current_peer Tff7b72= Te6edf3dest_hash
Tff7b72if Te6edf3announce_changeTb4b4b4:
Te6edf3name Tff7b72= Te6edf3_peer_namesTff7b72.Td2a8ffgetTb4b4b4(Te6edf3dest_hashTb4b4b4, Ta5d6ff"Ta5d6ff?Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff[target -> Ta5d6ff" Tff7b72+ Te6edf3name Tff7b72+ Ta5d6ff"Ta5d6ff/Ta5d6ff" Tff7b72+ Te6edf3_shortTb4b4b4(Te6edf3dest_hashTb4b4b4) Tff7b72+ Ta5d6ff"Ta5d6ff]Ta5d6ff"Tb4b4b4)


Tff7b72async Tff7b72def Td2a8ff_sendTb4b4b4(Te6edf3routerTb4b4b4, Te6edf3dest_hashTb4b4b4, Te6edf3bodyTb4b4b4)Tb4b4b4:
T8b949e"""Send an LXMF message asynchronously."""
Tff7b72import T7ee787uasyncio Tff7b72as T7ee787asyncio
Tff7b72await Te6edf3asyncioTff7b72.Td2a8ffsleepTb4b4b4(T79c0ff0Tb4b4b4)
Tff7b72tryTb4b4b4:
Te6edf3msg Tff7b72= Te6edf3routerTff7b72.Td2a8ffsend_messageTb4b4b4(Te6edf3dest_hashTb4b4b4, Te6edf3bodyTb4b4b4)
Tff7b72if Tff7b72not Te6edf3msgTb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff[err: unknown identity Ta5d6ff" Tff7b72+ Te6edf3_shortTb4b4b4(Te6edf3dest_hashTb4b4b4) Tff7b72+ Ta5d6ff"Ta5d6ff]Ta5d6ff"Tb4b4b4)
Tff7b72elif Te6edf3SHOW_OUTGOINGTb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff[me -> Ta5d6ff" Tff7b72+ Te6edf3_shortTb4b4b4(Te6edf3dest_hashTb4b4b4) Tff7b72+ Ta5d6ff"Ta5d6ff] Ta5d6ff" Tff7b72+ Te6edf3bodyTb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff[err: send failed: Ta5d6ff" Tff7b72+ Tffa657strTb4b4b4(Te6edf3eTb4b4b4) Tff7b72+ Ta5d6ff"Ta5d6ff]Ta5d6ff"Tb4b4b4)
Te6edf3gcTff7b72.Td2a8ffcollectTb4b4b4(Tb4b4b4)


T8b949e# ---- Serial chat loop -------------------------------------------------

Tff7b72async Tff7b72def Td2a8ffserial_chat_loopTb4b4b4(Te6edf3routerTb4b4b4)Tb4b4b4:
T8b949e"""Poll USB CDC stdin, dispatch /commands or send chat lines."""
Tff7b72import T7ee787sys
Tff7b72import T7ee787select
Tff7b72import T7ee787uasyncio Tff7b72as T7ee787asyncio
Tff7b72from T7ee787urnsT7ee787.T7ee787identity Tff7b72import Te6edf3Identity

Te6edf3poller Tff7b72= Te6edf3selectTff7b72.Td2a8ffpollTb4b4b4(Tb4b4b4)
Te6edf3pollerTff7b72.Td2a8ffregisterTb4b4b4(Te6edf3sysTff7b72.Td2a8ffstdinTb4b4b4, Te6edf3selectTff7b72.Td2a8ffPOLLINTb4b4b4)
Te6edf3buf Tff7b72= Ta5d6ff"Ta5d6ff"

Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ffuReticulum chat proxy ready. Type /help for commands.Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ffMy address: Ta5d6ff" Tff7b72+ Te6edf3routerTff7b72.Td2a8ffdelivery_destinationTff7b72.Td2a8ffhexhashTb4b4b4)
Tff7b72if Tff7b72not Te6edf3AUTO_ANNOUNCETb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff(auto-announce disabled — type /announce to broadcast)Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff"Tb4b4b4)

Tff7b72while Tff7b72TrueTb4b4b4:
Tff7b72tryTb4b4b4:
Tff7b72if Te6edf3pollerTff7b72.Td2a8ffpollTb4b4b4(T79c0ff0Tb4b4b4)Tb4b4b4:
Te6edf3ch Tff7b72= Te6edf3sysTff7b72.Td2a8ffstdinTff7b72.Td2a8ffreadTb4b4b4(T79c0ff1Tb4b4b4)
Tff7b72if Te6edf3ch Tff7b72in Tb4b4b4(Ta5d6ff"Tffea00\nTa5d6ff"Tb4b4b4, Ta5d6ff"Tffea00\rTa5d6ff"Tb4b4b4)Tb4b4b4:
Te6edf3line Tff7b72= Te6edf3bufTff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)
Te6edf3buf Tff7b72= Ta5d6ff"Ta5d6ff"
Tff7b72if Tff7b72not Te6edf3lineTb4b4b4:
Tff7b72pass

Tff7b72elif Te6edf3line Tff7b72== Ta5d6ff"Ta5d6ff/helpTa5d6ff"Tb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ffcommands:Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff /peers list known peersTa5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff /to <hex> set chat target by hash prefixTa5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff /me show my LXMF addressTa5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff /name show my display nameTa5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff /announce broadcast my identity nowTa5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff /quit exit to REPLTa5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff <anything else> send as chat to current targetTa5d6ff"Tb4b4b4)

Tff7b72elif Te6edf3line Tff7b72== Ta5d6ff"Ta5d6ff/meTa5d6ff"Tb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ffmy address: Ta5d6ff" Tff7b72+ Te6edf3routerTff7b72.Td2a8ffdelivery_destinationTff7b72.Td2a8ffhexhashTb4b4b4)

Tff7b72elif Te6edf3line Tff7b72== Ta5d6ff"Ta5d6ff/nameTa5d6ff"Tb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ffmy name: Ta5d6ff" Tff7b72+ Te6edf3NODE_NAMETb4b4b4)

Tff7b72elif Te6edf3line Tff7b72== Ta5d6ff"Ta5d6ff/peersTa5d6ff"Tb4b4b4:
Tff7b72if Tff7b72not Te6edf3IdentityTff7b72.Td2a8ffknown_destinationsTb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff[no peers known yet]Ta5d6ff"Tb4b4b4)
Tff7b72elseTb4b4b4:
Tff7b72for Te6edf3dh Tff7b72in Te6edf3IdentityTff7b72.Td2a8ffknown_destinationsTb4b4b4:
Te6edf3name Tff7b72= Te6edf3_peer_namesTff7b72.Td2a8ffgetTb4b4b4(Te6edf3dhTb4b4b4, Ta5d6ff"Ta5d6ff?Ta5d6ff"Tb4b4b4)
Te6edf3marker Tff7b72= Ta5d6ff"Ta5d6ff *Ta5d6ff" Tff7b72if Te6edf3dh Tff7b72== Te6edf3_current_peer Tff7b72else Ta5d6ff"Ta5d6ff"
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff Ta5d6ff" Tff7b72+ Te6edf3_shortTb4b4b4(Te6edf3dhTb4b4b4) Tff7b72+ Ta5d6ff"Ta5d6ff Ta5d6ff" Tff7b72+ Te6edf3name Tff7b72+ Te6edf3markerTb4b4b4)

Tff7b72elif Te6edf3lineTff7b72.Td2a8ffstartswithTb4b4b4(Ta5d6ff"Ta5d6ff/to Ta5d6ff"Tb4b4b4)Tb4b4b4:
Te6edf3pfx Tff7b72= Te6edf3lineTb4b4b4[T79c0ff4Tb4b4b4:Tb4b4b4]Tff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)Tff7b72.Td2a8fflowerTb4b4b4(Tb4b4b4)
Te6edf3found Tff7b72= Tff7b72None
Tff7b72for Te6edf3dh Tff7b72in Te6edf3IdentityTff7b72.Td2a8ffknown_destinationsTb4b4b4:
Tff7b72if Te6edf3dhTff7b72.Td2a8ffhexTb4b4b4(Tb4b4b4)Tff7b72.Td2a8ffstartswithTb4b4b4(Te6edf3pfxTb4b4b4)Tb4b4b4:
Te6edf3found Tff7b72= Te6edf3dh
Tff7b72break
Tff7b72if Te6edf3foundTb4b4b4:
Te6edf3_set_peerTb4b4b4(Te6edf3foundTb4b4b4)
Tff7b72elseTb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff[no peer matches Ta5d6ff'Ta5d6ff" Tff7b72+ Te6edf3pfx Tff7b72+ Ta5d6ff"Ta5d6ff'Ta5d6ff]Ta5d6ff"Tb4b4b4)

Tff7b72elif Te6edf3line Tff7b72== Ta5d6ff"Ta5d6ff/announceTa5d6ff"Tb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff[announcing...]Ta5d6ff"Tb4b4b4)
Tff7b72tryTb4b4b4:
Te6edf3routerTff7b72.Td2a8ffannounceTb4b4b4(Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff[announced]Ta5d6ff"Tb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff[err: Ta5d6ff" Tff7b72+ Tffa657strTb4b4b4(Te6edf3eTb4b4b4) Tff7b72+ Ta5d6ff"Ta5d6ff]Ta5d6ff"Tb4b4b4)
Te6edf3gcTff7b72.Td2a8ffcollectTb4b4b4(Tb4b4b4)

Tff7b72elif Te6edf3line Tff7b72== Ta5d6ff"Ta5d6ff/quitTa5d6ff"Tb4b4b4:
Tff7b72raise Tf85149KeyboardInterrupt

Tff7b72elif Te6edf3lineTff7b72.Td2a8ffstartswithTb4b4b4(Ta5d6ff"Ta5d6ff/Ta5d6ff"Tb4b4b4)Tb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff[unknown command: Ta5d6ff" Tff7b72+ Te6edf3line Tff7b72+ Ta5d6ff"Ta5d6ff]Ta5d6ff"Tb4b4b4)

Tff7b72elseTb4b4b4:
Tff7b72if Te6edf3_current_peer Tff7b72is Tff7b72NoneTb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff[no target — /peers then /to <prefix>]Ta5d6ff"Tb4b4b4)
Tff7b72elseTb4b4b4:
Te6edf3asyncioTff7b72.Td2a8ffcreate_taskTb4b4b4(
Te6edf3_sendTb4b4b4(Te6edf3routerTb4b4b4, Te6edf3_current_peerTb4b4b4, Te6edf3lineTb4b4b4)Tb4b4b4)
Tff7b72elseTb4b4b4:
Te6edf3buf Tff7b72+Tff7b72= Te6edf3ch

Tff7b72except Tf85149KeyboardInterruptTb4b4b4:
Tff7b72raise
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff[err: Ta5d6ff" Tff7b72+ Tffa657strTb4b4b4(Te6edf3eTb4b4b4) Tff7b72+ Ta5d6ff"Ta5d6ff]Ta5d6ff"Tb4b4b4)

Tff7b72await Te6edf3asyncioTff7b72.Td2a8ffsleepTb4b4b4(T79c0ff0.05Tb4b4b4)


T8b949e# ---- Reticulum / LXMF setup -------------------------------------------

Tff7b72def Td2a8ffsetup_nodeTb4b4b4(Te6edf3rnsTb4b4b4)Tb4b4b4:
Tff7b72from T7ee787urnsT7ee787.T7ee787lxmf Tff7b72import Te6edf3LXMRouter
Te6edf3router Tff7b72= Te6edf3LXMRouterTb4b4b4(Te6edf3identityTff7b72=Te6edf3rnsTff7b72.Td2a8ffidentityTb4b4b4)
Te6edf3dest Tff7b72= Te6edf3routerTff7b72.Td2a8ffregister_delivery_identityTb4b4b4(Te6edf3rnsTff7b72.Td2a8ffidentityTb4b4b4,
Te6edf3display_nameTff7b72=Te6edf3NODE_NAMETb4b4b4)

Tff7b72def Td2a8ffon_messageTb4b4b4(Te6edf3messageTb4b4b4)Tb4b4b4:
Te6edf3sender Tff7b72= Te6edf3messageTff7b72.Td2a8ffsource_hash
Te6edf3content Tff7b72= Te6edf3messageTff7b72.Td2a8ffcontent_as_stringTb4b4b4(Tb4b4b4) Tff7b72or Ta5d6ff"Ta5d6ff(binary)Ta5d6ff"
Te6edf3name Tff7b72= Te6edf3_peer_namesTff7b72.Td2a8ffgetTb4b4b4(Te6edf3senderTb4b4b4, Te6edf3_shortTb4b4b4(Te6edf3senderTb4b4b4)Tb4b4b4)
Te6edf3verified Tff7b72= Ta5d6ff"Ta5d6ff" Tff7b72if Te6edf3messageTff7b72.Td2a8ffsignature_validated Tff7b72else Ta5d6ff"Ta5d6ff [UNVERIFIED]Ta5d6ff"
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff<Ta5d6ff" Tff7b72+ Te6edf3name Tff7b72+ Ta5d6ff"Ta5d6ff>Ta5d6ff" Tff7b72+ Te6edf3verified Tff7b72+ Ta5d6ff"Ta5d6ff Ta5d6ff" Tff7b72+ Te6edf3contentTb4b4b4)

T8b949e# Auto-target sender so a reply just works
Tff7b72if Te6edf3_current_peer Tff7b72!= Te6edf3senderTb4b4b4:
Te6edf3_set_peerTb4b4b4(Te6edf3senderTb4b4b4, Te6edf3announce_changeTff7b72=Tff7b72TrueTb4b4b4)
Te6edf3gcTff7b72.Td2a8ffcollectTb4b4b4(Tb4b4b4)

Te6edf3routerTff7b72.Td2a8ffregister_delivery_callbackTb4b4b4(Te6edf3on_messageTb4b4b4)

Tff7b72def Td2a8ffon_announceTb4b4b4(Te6edf3destination_hashTb4b4b4, Te6edf3display_nameTb4b4b4)Tb4b4b4:
Tff7b72if Te6edf3display_nameTb4b4b4:
Te6edf3_peer_namesTb4b4b4[Te6edf3destination_hashTb4b4b4] Tff7b72= Te6edf3display_name
T8b949e# Acquire target on first announce
Tff7b72if Te6edf3_current_peer Tff7b72is Tff7b72NoneTb4b4b4:
Tff7b72if Te6edf3DEFAULT_PEERTb4b4b4:
Tff7b72tryTb4b4b4:
Tff7b72if Te6edf3destination_hash Tff7b72== Tffa657bytesTff7b72.Td2a8fffromhexTb4b4b4(Te6edf3DEFAULT_PEERTb4b4b4)Tb4b4b4:
Te6edf3_set_peerTb4b4b4(Te6edf3destination_hashTb4b4b4)
Tff7b72except Tf85149ExceptionTb4b4b4:
Tff7b72pass
Tff7b72elseTb4b4b4:
Te6edf3_set_peerTb4b4b4(Te6edf3destination_hashTb4b4b4)
Tff7b72elseTb4b4b4:
T8b949e# Just note the new peer quietly
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff[peer: Ta5d6ff" Tff7b72+ Tb4b4b4(Te6edf3display_name Tff7b72or Ta5d6ff"Ta5d6ff?Ta5d6ff"Tb4b4b4) Tff7b72+
Ta5d6ff"Ta5d6ff Ta5d6ff" Tff7b72+ Te6edf3_shortTb4b4b4(Te6edf3destination_hashTb4b4b4) Tff7b72+ Ta5d6ff"Ta5d6ff]Ta5d6ff"Tb4b4b4)

Te6edf3routerTff7b72.Td2a8ffregister_announce_callbackTb4b4b4(Te6edf3on_announceTb4b4b4)
Tff7b72return Te6edf3destTb4b4b4, Te6edf3router


T8b949e# ---- Main -------------------------------------------------------------

Tff7b72def Td2a8ffmainTb4b4b4(Tb4b4b4)Tb4b4b4:
Tff7b72import T7ee787uasyncio Tff7b72as T7ee787asyncio
Tff7b72from T7ee787urns Tff7b72import Te6edf3Reticulum
Tff7b72from T7ee787urnsT7ee787.T7ee787log Tff7b72import Te6edf3LOG_NONE

T8b949e# Force quiet operation regardless of what config.py says
Te6edf3quiet_config Tff7b72= Tffa657dictTb4b4b4(Te6edf3CONFIGTb4b4b4)
Te6edf3quiet_configTb4b4b4[Ta5d6ff"Ta5d6ffloglevelTa5d6ff"Tb4b4b4] Tff7b72= T79c0ff0

Te6edf3rns Tff7b72= Te6edf3ReticulumTb4b4b4(Te6edf3loglevelTff7b72=Te6edf3LOG_NONETb4b4b4)
Te6edf3rnsTff7b72.Td2a8ffconfig Tff7b72= Te6edf3quiet_config

Te6edf3destTb4b4b4, Te6edf3router Tff7b72= Te6edf3setup_nodeTb4b4b4(Te6edf3rnsTb4b4b4)
Te6edf3rnsTff7b72.Td2a8ffsetup_interfacesTb4b4b4(Tb4b4b4)
Te6edf3gcTff7b72.Td2a8ffcollectTb4b4b4(Tb4b4b4)

Tff7b72async Tff7b72def Td2a8ffinitial_announceTb4b4b4(Tb4b4b4)Tb4b4b4:
Tff7b72await Te6edf3asyncioTff7b72.Td2a8ffsleepTb4b4b4(T79c0ff2Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff[auto-announcing... ~2s]Ta5d6ff"Tb4b4b4)
Tff7b72tryTb4b4b4:
Te6edf3routerTff7b72.Td2a8ffannounceTb4b4b4(Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff[announced]Ta5d6ff"Tb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff[announce err: Ta5d6ff" Tff7b72+ Tffa657strTb4b4b4(Te6edf3eTb4b4b4) Tff7b72+ Ta5d6ff"Ta5d6ff]Ta5d6ff"Tb4b4b4)
Te6edf3gcTff7b72.Td2a8ffcollectTb4b4b4(Tb4b4b4)

Tff7b72async Tff7b72def Td2a8ffreannounce_loopTb4b4b4(Tb4b4b4)Tb4b4b4:
Tff7b72while Tff7b72TrueTb4b4b4:
Tff7b72await Te6edf3asyncioTff7b72.Td2a8ffsleepTb4b4b4(T79c0ff120Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff[re-announcing...]Ta5d6ff"Tb4b4b4)
Tff7b72tryTb4b4b4:
Te6edf3routerTff7b72.Td2a8ffannounceTb4b4b4(Tb4b4b4)
Tff7b72except Tf85149ExceptionTb4b4b4:
Tff7b72pass
Te6edf3gcTff7b72.Td2a8ffcollectTb4b4b4(Tb4b4b4)

Tff7b72async Tff7b72def Td2a8ffrun_allTb4b4b4(Tb4b4b4)Tb4b4b4:
Tff7b72from T7ee787urnsT7ee787.T7ee787transport Tff7b72import Te6edf3Transport

T8b949e# Create all tasks independently — no gather(), so one dying
T8b949e# doesn't kill the rest
Te6edf3asyncioTff7b72.Td2a8ffcreate_taskTb4b4b4(Te6edf3TransportTff7b72.Td2a8ffjob_loopTb4b4b4(Tb4b4b4)Tb4b4b4)
Te6edf3asyncioTff7b72.Td2a8ffcreate_taskTb4b4b4(Te6edf3serial_chat_loopTb4b4b4(Te6edf3routerTb4b4b4)Tb4b4b4)

Tff7b72for Te6edf3iface Tff7b72in Te6edf3rnsTff7b72.Td2a8ffinterfacesTb4b4b4:
Tff7b72if Tffa657hasattrTb4b4b4(Te6edf3ifaceTb4b4b4, Ta5d6ff'Ta5d6ffpoll_loopTa5d6ff'Tb4b4b4)Tb4b4b4:
Te6edf3asyncioTff7b72.Td2a8ffcreate_taskTb4b4b4(Te6edf3ifaceTff7b72.Td2a8ffpoll_loopTb4b4b4(Tb4b4b4)Tb4b4b4)

Tff7b72if Te6edf3AUTO_ANNOUNCETb4b4b4:
Te6edf3asyncioTff7b72.Td2a8ffcreate_taskTb4b4b4(Te6edf3initial_announceTb4b4b4(Tb4b4b4)Tb4b4b4)
Te6edf3asyncioTff7b72.Td2a8ffcreate_taskTb4b4b4(Te6edf3reannounce_loopTb4b4b4(Tb4b4b4)Tb4b4b4)

Te6edf3n Tff7b72= Tffa657lenTb4b4b4(Te6edf3TransportTff7b72.Td2a8ffinterfacesTb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff[tasks started, Ta5d6ff" Tff7b72+ Tffa657strTb4b4b4(Te6edf3nTb4b4b4) Tff7b72+ Ta5d6ff"Ta5d6ff interface(s)]Ta5d6ff"Tb4b4b4)

T8b949e# Keep the event loop alive forever
Tff7b72while Tff7b72TrueTb4b4b4:
Tff7b72await Te6edf3asyncioTff7b72.Td2a8ffsleepTb4b4b4(T79c0ff10Tb4b4b4)

Tff7b72tryTb4b4b4:
Te6edf3asyncioTff7b72.Td2a8ffrunTb4b4b4(Te6edf3run_allTb4b4b4(Tb4b4b4)Tb4b4b4)
Tff7b72except Tf85149KeyboardInterruptTb4b4b4:
Te6edf3rnsTff7b72.Td2a8ffshutdownTb4b4b4(Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff[shutdown]Ta5d6ff"Tb4b4b4)


Te6edf3mainTb4b4b4(Tb4b4b4)

Served by rngit 1.5.2 - Generated in 0.05s